package com.tlgen.orm.components.sqlXml;

import com.tlgen.orm.utils.log.ColorLogger;
import com.tlgen.orm.utils.xml.XmlFileReader;
import lombok.var;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

@Component
public class XmlConfigManager implements ApplicationContextAware, InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(XmlConfigManager.class);

    // 添加资源目录路径
    private static Path RESOURCE_DIR;

    // 环境检测标志（true=开发环境，false=生产环境）
    private static final boolean IS_DEV_MODE = detectDevelopmentEnvironment();

    // 全局XML缓存（key为绝对路径）
    private static final Map<String, XmlContent> GLOBAL_XML_MAP = new ConcurrentHashMap<>();

    // 在类顶部添加防抖时间常量
    private static final long CHANGE_COOLDOWN = 500; // 500ms防抖时间
    private static final Map<String, Long> lastChangeTimeMap = new ConcurrentHashMap<>();

    private ApplicationContext applicationContext;
    private ScheduledExecutorService scheduler;
    private WatchService watchService;
    private final AtomicBoolean watching = new AtomicBoolean(true);
    private Path xmlDir;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        try {
            // 1. 解析XML目录
            this.xmlDir = resolveXmlDirectory();
            RESOURCE_DIR = this.xmlDir; // 保存资源目录
//            logger.info("XML配置目录: {}", xmlDir);

            // 2. 加载初始XML配置
            loadInitialXmlFiles();

            // 3. 只有是文件系统路径才启动监听
            if (xmlDir != null && Files.isDirectory(xmlDir)) {
                initFileWatcher();
            } else {
//                logger.warn("XML配置目录不是文件系统目录或非开发环境，不启动文件监听");
            }
        } catch (Exception e) {
//            logger.error("XML配置管理器初始化失败", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 检测开发环境
     */
    public static boolean detectDevelopmentEnvironment() {
        try {
            // 方法1: 检查是否从IDE运行
            String classPath = System.getProperty("java.class.path", "");
            if (classPath.contains("idea_rt.jar") || classPath.contains("eclipse")) {
                return true;
            }

            // 方法2: 检查源码目录是否存在
            Path sourcePath = Paths.get("src/main/resources");
            if (Files.exists(sourcePath) && Files.isDirectory(sourcePath)) {
                return true;
            }

            // 方法3: 检查调试参数
            List<String> inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
            for (String arg : inputArguments) {
                if (arg.contains("-agentlib:jdwp")) {
                    return true;
                }
            }

            return false;
        } catch (Exception e) {
            log.error("环境检测失败", e);
            return false;
        }
    }

    // ================= 路径解析方法（环境适配） =================

    /**
     * 更健壮的路径解析方法（解决开发环境路径问题）
     */
    private Path resolveXmlDirectory() {
        try {
            // 开发环境特殊处理：直接使用源代码目录
            if (IS_DEV_MODE) {
                Path devPath = Paths.get(System.getProperty("user.dir"), "src", "main", "resources", "SQL-XML");
                if (Files.exists(devPath)) {
//                    log.info("开发环境使用源代码目录: {}", devPath);
                    return devPath;
                }
            }

            // 生产环境处理
            Resource resource = applicationContext.getResource("classpath:/SQL-XML/");

            // 处理jar内部的资源（不支持监听）
            if (resource.getURI().toString().contains(".jar!")) {
                log.warn("生产环境: JAR内部的XML资源不支持热更新");
                return null;
            }

            // 处理文件系统上的资源
            Path filePath = Paths.get(resource.getURI());
            if (Files.isDirectory(filePath)) {
                log.info("生产环境使用配置目录: {}", filePath);
                return filePath;
            }

            // 使用工作目录作为后备
            Path defaultDir = Paths.get(System.getProperty("user.dir"), "config", "SQL-XML");
            log.warn("使用默认配置目录: {}", defaultDir);
            if (!Files.exists(defaultDir)) {
                Files.createDirectories(defaultDir);
            }
            return defaultDir;

        } catch (Exception e) {
            log.error("解析XML目录失败", e);
            return Paths.get(System.getProperty("user.dir")).resolve("config");
        }
    }

    private void initFileWatcher() throws IOException {
//        logger.info("启动XML文件监听器...");
        if (!IS_DEV_MODE) return;

        // 创建WatchService
        watchService = FileSystems.getDefault().newWatchService();

        // 递归注册目录（包括子目录）
        registerDirectoryRecursive(xmlDir);

        // 创建线程池处理事件
        scheduler = Executors.newScheduledThreadPool(2);

        // 启动监听线程
        Thread watchThread = new Thread(this::watchFiles, "XML-Config-Watcher");
        watchThread.setDaemon(true);
        watchThread.start();

//        logger.info("XML配置监听器已启动，监控目录: {}", xmlDir);
    }

    private void registerDirectoryRecursive(Path dir) {
        if (dir == null || !Files.isDirectory(dir)) {
            return;
        }

        try {
            // 先注册当前目录
            dir.register(watchService,
                    StandardWatchEventKinds.ENTRY_CREATE,
                    StandardWatchEventKinds.ENTRY_MODIFY,
                    StandardWatchEventKinds.ENTRY_DELETE);

//            logger.debug("已注册目录监听: {}", dir);

            // 递归注册子目录
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
                for (Path entry : stream) {
                    if (Files.isDirectory(entry)) {
                        registerDirectoryRecursive(entry);
                    }
                }
            }
        } catch (IOException e) {
            log.warn("无法监控目录: {}", dir, e);
        }
    }

    // 修改 handleXmlChange 方法
    private void handleXmlChange(Path changedFile) {
        try {
            Path absPath = changedFile.toAbsolutePath().normalize();
            String absoluteKey = absPath.toString();

            // 1. 文件名过滤和有效性检查
            if (absoluteKey.isEmpty() || !Files.isRegularFile(absPath)
                    || !absoluteKey.toLowerCase().endsWith(".xml") || isExcludedPath(absoluteKey)) {
                return;
            }

            // 2. 时间戳防抖机制 - 同一文件500ms内只处理一次
            long currentTime = System.currentTimeMillis();
            Long lastChangeTime = lastChangeTimeMap.get(absoluteKey);

            if (lastChangeTime != null && (currentTime - lastChangeTime) < CHANGE_COOLDOWN) {
                return; // 跳过短时间内的重复事件
            }
            lastChangeTimeMap.put(absoluteKey, currentTime);

            // 3. 获取文件最后修改时间（带重试）
            long lastModified = getFileModifiedTimeWithRetry(absPath);

            // 4. 检查缓存版本防止重复处理
            XmlContent oldContent = GLOBAL_XML_MAP.get(absoluteKey);

            if (oldContent != null) {
                long timeDiff = Math.abs(lastModified - oldContent.lastModified);

                // 4.1 检查时间戳差异
                if (timeDiff < 1000) {
                    try {
                        // 4.2 内容比较防抖 - 即使时间接近也检查内容
                        String currentContent = readFileContent(absPath);
                        if (currentContent.equals(oldContent.data)) {
                            return; // 内容相同则跳过
                        }
                    } catch (Exception e) {
                        // 忽略内容比较的异常
                    }
                }
            }

            // 5. 实际的修改处理逻辑（这里是单次调用的核心）
            log.debug("处理文件变更: {}", absoluteKey);
            String newContent = readFileWithRetry(absPath);

            // 宽松的XML验证
            if (!isValidXml(newContent)) {
                log.warn("XML验证失败: {}", absoluteKey);
                return;
            }

            // 6. 更新缓存
            XmlContent newData = new XmlContent(absoluteKey, newContent, lastModified);
            GLOBAL_XML_MAP.put(absoluteKey, newData);

            long endTime = System.currentTimeMillis();
            long reloadDuration = endTime - currentTime;

            // 7. 确保单次打印（这里是触发打印的位置）
            ColorLogger.vscodeXmlReloadedSuccess(reloadDuration);

            // 8. 清理防抖缓存（设置较短的有效期）
            scheduler.schedule(() -> lastChangeTimeMap.remove(absoluteKey),
                    CHANGE_COOLDOWN + 100, TimeUnit.MILLISECONDS);

        } catch (Exception e) {
            log.warn("处理文件变更失败: {}", changedFile, e);
        }
    }

    private boolean isExcludedPath(String path) {
        // 排除常见IDE运行时文件
        return path.contains("idea_rt.jar") ||
                path.contains("javafx-runtime") ||
                path.contains("/.idea/") ||
                path.contains("~$") || // Office 临时文件
                path.endsWith(".tmp"); // 通用临时文件
    }

    private long getFileModifiedTimeWithRetry(Path path) throws IOException {
        int retry = 0;
        while (retry < 3) {
            try {
                return Files.getLastModifiedTime(path).toMillis();
            } catch (IOException e) {
                retry++;
                log.warn("获取文件修改时间失败，重试 {}: {}", retry, path);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        throw new IOException("无法获取文件修改时间: " + path);
    }

    private String readFileWithRetry(Path path) throws IOException {
        int retry = 0;
        while (retry < 3) {
            try {
                return XmlFileReader.readXmlFile(path);
            } catch (AccessDeniedException e) {
                retry++;
                log.warn("文件访问被拒绝，重试 {}: {}", retry, path);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        throw new IOException("文件读取失败: " + path);
    }

    private String readFileContent(Path path) {
        try {
            return XmlFileReader.readXmlFile(path);
        } catch (IOException e) {
            log.warn("读取文件内容失败: {}", path, e);
            return "";
        }
    }

    private boolean isValidXml(String content) {
        if (content == null || content.trim().isEmpty()) {
            return false;
        }

        // 宽松的XML验证
        return content.trim().startsWith("<") &&
                content.contains(">") &&
                content.contains("</");
    }

    /**
     * 提供获取XML内容的方法（环境适配）
     */
    public static String getXmlContent(String relativePath) {
        // 开发环境：使用资源目录 + 相对路径
        if (IS_DEV_MODE) {
            try {
                if (RESOURCE_DIR != null) {
                    Path filePath = RESOURCE_DIR.resolve(relativePath);
                    return new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8);
                }
            } catch (IOException e) {
                log.warn("开发环境读取XML失败: {}", relativePath, e);
            }
        }

        // 生产环境：从缓存或类路径读取
        XmlContent content = GLOBAL_XML_MAP.get(relativePath);
        if (content != null) {
            return content.data;
        }

        // 缓存未命中时从类路径读取
        try {
            InputStream inputStream = XmlConfigManager.class.getClassLoader()
                    .getResourceAsStream("SQL-XML/" + relativePath);

            if (inputStream != null) {
                return readInputStream(inputStream);
            }
        } catch (IOException e) {
            log.error("生产环境读取XML失败: {}", relativePath, e);
        }

        return null;
    }

    // 兼容 Java 8 的替代方案
    public static String readInputStream(InputStream inputStream) throws IOException {
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }
        return result.toString(StandardCharsets.UTF_8.name());
    }

    // 安全加载初始配置文件
    private void loadInitialXmlFiles() {
        if (xmlDir == null) {
//            logger.warn("XML配置目录未指定，跳过初始加载");
            return;
        }

//        logger.info("开始加载初始XML配置...");
        AtomicInteger loadedCount = new AtomicInteger(0);
        AtomicInteger skippedCount = new AtomicInteger(0);

        try {
            if (Files.isDirectory(xmlDir)) {
                try (var paths = Files.walk(xmlDir)) {
                    paths.filter(p -> Files.isRegularFile(p) && p.toString().toLowerCase().endsWith(".xml"))
                            .forEach(path -> {
                                try {
                                    String absoluteKey = path.toAbsolutePath().toString();

                                    // 跳过IDE运行时文件
                                    if (isExcludedPath(absoluteKey)) {
                                        skippedCount.incrementAndGet();
                                        log.debug("跳过IDE文件: {}", absoluteKey);
                                        return;
                                    }

                                    // 跳过无法解析的文件
                                    if (absoluteKey.endsWith(".iml") || absoluteKey.contains("$")) {
                                        skippedCount.incrementAndGet();
                                        return;
                                    }

                                    String content = XmlFileReader.readXmlFile(path);
                                    long lastModified = Files.getLastModifiedTime(path).toMillis();

                                    // 初始化缓存（包含绝对路径）
                                    XmlContent xmlContent = new XmlContent(absoluteKey, content, lastModified);
                                    GLOBAL_XML_MAP.put(absoluteKey, xmlContent);

                                    loadedCount.incrementAndGet();
                                    log.debug("初始加载XML: {}", absoluteKey);
                                } catch (Exception e) {
                                    skippedCount.incrementAndGet();
                                    log.warn("加载XML文件失败: {}", path, e);
                                }
                            });
                }
            } else {
                log.warn("XML配置目录不是文件系统目录: {}", xmlDir);
            }
        } catch (Exception e) {
            log.error("初始加载XML配置异常", e);
        }

//        logger.info("初始XML加载完成: 成功加载 {} 个文件, 跳过 {} 个文件", loadedCount.get(), skippedCount.get());
    }

    private void watchFiles() {
//        logger.info("文件监听线程启动...");

        try {
            while (watching.get()) {
                WatchKey key = watchService.poll(5, TimeUnit.SECONDS);
                if (key == null) continue;

                for (WatchEvent<?> event : key.pollEvents()) {
                    // 跳过溢出事件
                    if (event.kind() == StandardWatchEventKinds.OVERFLOW) continue;

                    // 获取事件文件的实际路径
                    Path watchPath = (Path) key.watchable();
                    Path eventPath = (Path) event.context();

                    // 处理目录创建事件
                    if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                        Path fullPath = watchPath.resolve(eventPath);
                        if (Files.isDirectory(fullPath)) {
                            // 重新注册新创建的目录
                            registerDirectoryRecursive(fullPath);
                        }
                    }

                    // 处理所有事件
                    Path changedFile = watchPath.resolve(eventPath);

                    // 延迟处理（避免编辑器多次保存）
                    scheduler.schedule(() -> {
                        try {
                            handleXmlChange(changedFile);
                        } catch (Exception e) {
                            log.error("处理文件事件失败", e);
                        }
                    }, 300, TimeUnit.MILLISECONDS);
                }

                // 处理键失效
                if (!key.reset()) {
//                    logger.warn("WatchKey 已无效，尝试重新注册目录...");
                    registerDirectoryRecursive(xmlDir);
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.info("文件监听线程被中断");
        } catch (Exception e) {
            log.error("文件监听线程异常", e);
        } finally {
            log.info("文件监听线程结束");
        }
    }

    private void shutdown() {
        log.info("关闭XML配置管理器...");
        watching.set(false);

        if (watchService != null) {
            try {
                watchService.close();
            } catch (IOException e) {
                log.warn("关闭WatchService失败", e);
            }
        }

        if (scheduler != null) {
            scheduler.shutdownNow();
            try {
                if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                    log.warn("文件监听线程池未正常终止");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        log.info("XML配置管理器已关闭");
    }

    // 带绝对路径的内部类
    public static class XmlContent {
        public final String absolutePath; // 公开访问
        public final String data;
        public final long lastModified;

        public XmlContent(String absolutePath, String data, long lastModified) {
            this.absolutePath = absolutePath;
            this.data = data;
            this.lastModified = lastModified;
        }

        @Override
        public String toString() {
            return "XmlContent{" +
                    "path='" + absolutePath + '\'' +
                    ", size=" + (data != null ? data.length() : 0) +
                    ", modified=" + lastModified +
                    '}';
        }
    }

}